#include "CCProtocol.h"

//globals
CProtocol g_myPrt;
CProtocol g_otherPrt;
int g_imCurrentlyWriting;
int g_imCurrentlyListening;

void commitProtocol(CProtocol* p) {
	byte bits = 0;
	bits |= p->errFeedback << 2;
	bits |= p->readStatus << 3;
	bits |= p->writeStatus << 4;
	bits |= p->data & 3;
	outb(bits, 0x378);
}

void readProtocol(CProtocol* out) {
	byte bits;
	bits = inb(0x379);
	// shift-left bits since bits are read shift-righted
	bits >>= 3;
	bits ^= (1 << 4);
	out->errFeedback	= (bits & (1 << 2)) >> 2;
	out->readStatus		= (bits & (1 << 3)) >> 3;
	out->writeStatus	= (bits & (1 << 4)) >> 4;
	out->data 		= getTwoBits(bits, 0);
}

byte getTwoBits(byte from, int idx) {
	return (from >> (idx * 2)) & 3;
}

void setTwoBits(byte* from, int idx, byte val) {
	// set 2 idx-byte to 0
	*from = *from & (-(3 << (idx * 2)) - 1);
	// set 2 idx-byte to val
	*from = *from | ((val & 3) << (idx * 2));
}

//Atomic Test-and-set-lock
int TSL(int* lock) {
	if(!lock) {
		*lock = 1;
		return 1;
	}
	return 0;
}

void releaseLock(int* lock) {
	*lock = 0;
}

unsigned long getMillisecs(void) {
	unsigned long result;
	rdtscl(result);
	return result;
}

int tooLong(unsigned long startTime) {
	return getMillisecs() - startTime > TIMEOUT;
}

void initCProtocol() {
	g_myPrt.writeStatus 	= 0;
	g_myPrt.readStatus 	= 0;
	g_myPrt.errFeedback	= 0;
	commitProtocol(&g_myPrt);
}

int readAChar(byte* out) {
	//notify other I'm listening
	g_myPrt.readStatus = 1;
	commitProtocol(&g_myPrt);
	
	//read each bits, we expect the first writeStatus is 1
	int prevWriteStatus = 0;
	int i = 0;
	for(; i < 4; ++i) {
		//Wait until bits is sent by other
		unsigned long time = getMillisecs();
		int isTimeout = 0;
		do {
			isTimeout = tooLong(time);
			readProtocol(&g_otherPrt);
		} while(!isTimeout && g_otherPrt.writeStatus == prevWriteStatus);
		prevWriteStatus = g_otherPrt.writeStatus;
		
		//quit if timeout
		if(isTimeout) break;
		
		//read from other based on current iteration
		setTwoBits(out, i, g_otherPrt.data);
		
		//send ack by toggling read status
		g_myPrt.readStatus = !g_myPrt.readStatus;
		commitProtocol(&g_myPrt);
	}
	
	//reset variables
	g_myPrt.readStatus = 0;
	commitProtocol(&g_myPrt);
	
	//done!
	return i;
}

int writeAChar(byte in) {
	//if other is currently not listening, wait
	unsigned long time = getMillisecs();
	do {
		if(tooLong(time)) return 0; //fail
		readProtocol(&g_otherPrt);
	} while(g_otherPrt.readStatus != 1);

	//send bits to other
	int prevReadStatus = g_otherPrt.readStatus;
	g_myPrt.writeStatus= 0;
	int i = 0;
	for(; i < 4; ++i) {
		//set bits to send based on cur iteration
		g_myPrt.data = getTwoBits(in, i);
		
	 	//commit a new write by toggling writeStatus
		g_myPrt.writeStatus = !g_myPrt.writeStatus;
		commitProtocol(&g_myPrt);
		
		//wait for ack
		int isTimeout = 0;
		time = getMillisecs();
		do {
			isTimeout = tooLong(time);
			readProtocol(&g_otherPrt);
		} while(!isTimeout && g_otherPrt.readStatus == prevReadStatus);
		prevReadStatus = g_otherPrt.readStatus;
		
		//break if timeout
		if(isTimeout) break;
	}
	
	//reset variables
	g_myPrt.writeStatus = 0;
	commitProtocol(&g_myPrt);
	
	//done!
	return i;
}

int writeCheckSumAndGetFeedback(byte checkSum, byte* feedBack) {
	//write checksum
	int ret = writeAChar(checkSum);
	
	//last readProtocol contains errFeedback, so set to out
	if(ret == 4) {
		*feedBack = g_otherPrt.errFeedback;
	}
	
	//return
	return ret;
}

int getCheckSumAndWriteFeedback(byte expectedCheckSum, byte* myFeedback) {
	//notify other I'm listening
	g_myPrt.readStatus = 1;
	commitProtocol(&g_myPrt);
	byte oout;
	byte* out = &oout;
	
	//read each bits, we expect the first writeStatus is 1
	int prevWriteStatus = 0;
	int i = 0;
	for(; i < 4; ++i) {
		//Wait until bits is sent by other
		unsigned long time = getMillisecs();
		int isTimeout = 0;
		do {
			isTimeout = tooLong(time);
			readProtocol(&g_otherPrt);
		} while(!isTimeout && g_otherPrt.writeStatus == prevWriteStatus);
		prevWriteStatus = g_otherPrt.writeStatus;
		
		//quit if timeout
		if(isTimeout) break;
		
		//read from other based on current iteration
		setTwoBits(out, i, g_otherPrt.data);
		
		//if this is last iter, set feedback
		if(i == 3) {
			g_myPrt.errFeedback = *myFeedback = (*out == expectedCheckSum);
		}
		
		//send ack by toggling read status
		g_myPrt.readStatus = !g_myPrt.readStatus;
		commitProtocol(&g_myPrt);
	}
	
	//reset variables
	g_myPrt.readStatus = 0;
	commitProtocol(&g_myPrt);
	
	//done!
	return i;
}

ssize_t readABunch(byte* out, char* foundEOT, size_t len) {
	//read data
	ssize_t i = 0;
	byte got = 0;
	byte checkSum = 0;
	int succ = 1;

	for( ; succ && i < len; ++i) {
		//get a char
		succ = readAChar(&got) == 4;
		if(!succ) break;
		
		//break if it is EOT
		if(got == EOT) {
			*foundEOT = 1;
			break;
		}
	
		//read another char if it is ESC
		else if(got == ESC) succ = readAChar(&got) == 4;
		
		checkSum = (checkSum + got);
		out[i] = got;
	}
	
	//error control	
	if(succ) {
		byte feedback;
		succ = getCheckSumAndWriteFeedback(checkSum, &feedback) == 4;
		if(!feedback) i = ERROR_CHECKSUM;
	}
	
	return succ ? i : ERROR_SENDBITS;
}

ssize_t writeABunch(byte* in, size_t len) {		
	//write data
	ssize_t i = 0;
	byte checkSum = 0;
	int succ = 1;
	for( ; succ && i < len; ++i) {
		//send ESC if cur char should be escaped
		if(in[i] == ESC || in[i] == EOT) {
			succ = writeAChar(ESC) == 4;
			if(!succ) break;
		}
		
		//send current char
		succ = writeAChar(in[i]) == 4;
		checkSum = (checkSum + in[i]);
	}
	
	//send EOT
	succ = writeAChar(EOT) == 4;
	
	//error control
	if(succ) {
		byte feedback;
		succ = writeCheckSumAndGetFeedback(checkSum, &feedback) == 4;
		if(!feedback) i = ERROR_CHECKSUM;
	}
	
	//reading a bunch succeed, release lock
	return succ ? i : ERROR_SENDBITS;
}

ssize_t readCharSequence(byte* out, char* foundEOT, size_t len) {
	//test and set lock
	if(!TSL(&g_imCurrentlyListening))
		return ERROR_LOCKED;
	
	//slice by window size and read
	size_t windowCount = len / WINDOW_SIZE + (len % WINDOW_SIZE > 0 ? 0 : 1);
	ssize_t retval = 0;
	*foundEOT = 0;
	size_t i = 0;
	for(; !*foundEOT && i < windowCount; ++i) {
	
		//try reading TRY_COUNT times until no error
		byte OK = 0;
		byte* temp = out + i * WINDOW_SIZE;
		size_t llen= i * WINDOW_SIZE;
		llen = llen < len ? WINDOW_SIZE : WINDOW_SIZE - (llen - len);
		ssize_t ret= 0;
		int j = 0;
		for(; !OK && j < TRY_COUNT; ++j) {
			ret = readABunch(temp, foundEOT, llen);
			OK = ret != ERROR_CHECKSUM && ret != ERROR_SENDBITS;
		}
		
		//if exceed try count, read fail
		if(j >= TRY_COUNT) {
			retval = ret;
			break;
		} else {
			retval += ret;
		}
	}
	
	//reading a bunch done, release lock
	releaseLock(&g_imCurrentlyListening);
	return retval;
}

ssize_t writeCharSequence(byte* in, size_t len) {
	//test and set lock
	if(!TSL(&g_imCurrentlyWriting))
		return ERROR_LOCKED;
	
	//slice by window size and send
	size_t windowCount = len / WINDOW_SIZE + (len % WINDOW_SIZE > 0 ? 0 : 1);
	ssize_t retval = 0;
	size_t i = 0;
	for(; i < windowCount; ++i) {
	
		//try reading TRY_COUNT times until no error
		byte OK = 0;
		byte* temp = in + i * WINDOW_SIZE;
		size_t llen= i * WINDOW_SIZE;
		llen = llen < len ? WINDOW_SIZE : WINDOW_SIZE - (llen - len);
		ssize_t ret= 0;
		int j = 0;
		for(; !OK && j < TRY_COUNT; ++j) {
			ret = writeABunch(temp, llen);
			OK = ret != ERROR_CHECKSUM && ret != ERROR_SENDBITS;
		}
		
		//if exceed try count, read fail
		if(j >= TRY_COUNT) {
			retval = ret;
			break;
		} else {
			retval += ret;
		}
	}
	
	//reading a bunch done, release lock
	releaseLock(&g_imCurrentlyWriting);
	return retval;
}
